package com.scut.filedownloader.core;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.scut.filedownloader.FileDownloader;
import com.scut.filedownloader.core.cocurrent.SharedThreadPool;
import com.scut.filedownloader.core.interact.AbsDownloadMonitor;
import com.scut.filedownloader.core.interact.ProgressMonitor;
import com.scut.filedownloader.core.recorder.DownloadEntryRecorder;
import com.scut.filedownloader.core.recorder.DownloadRecorder;
import com.scut.filedownloader.util.FDFormatter;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

/**
 * 处理下载核心任务的功能型类，初始化需要传入Context对象以获得将必要数据持久化的接口
 * 该类采用单例模式, 通过getInstance(Context)获取
 */

public class Downloader{

    private Context mContext;

    static private Downloader downloader=null;
    private ConnectivityManager connectivityManager=null;
    private FileDownloader app=null;
    private File emulated_0_root=new File(FileDownloader.getInstance().defaultExternalStoragePath);

    private android.net.Network activeNetwork;

    private Downloader(Context context){
        this.mContext=context;
        app=FileDownloader.getInstance();
        //initialize
        connectivityManager= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        activeNetwork=connectivityManager.getActiveNetwork();

        setConnectivityStateChangeListener();
    }

    public void setConnectivityStateChangeListener(){
        class NetWorkStateChangeCallBack extends ConnectivityManager.NetworkCallback{
            @Override
            public void onLost(@NonNull Network network) {
                super.onLost(network);
                if(network.equals(activeNetwork)){
                    Toast.makeText(mContext, "Network becomes unavailable,check connection!", Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onAvailable(@NonNull Network network) {
                super.onAvailable(network);
                if(connectivityManager.getNetworkCapabilities(network).hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)){
                    activeNetwork=connectivityManager.getActiveNetwork();
                    Toast.makeText(mContext, "network: "+network.toString()+" become available", Toast.LENGTH_SHORT).show();
                }
            }
        }

        connectivityManager.registerDefaultNetworkCallback(new NetWorkStateChangeCallBack());
    }

    public boolean isNetWorkAvailable()
    {
        if(connectivityManager!=null){
            final NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo(); //deprecate in api29
            if(networkInfo!=null) {
                final int netType = networkInfo.getType();
                final NetworkInfo.State netState = networkInfo.getState();
                if(netType==ConnectivityManager.TYPE_WIFI||netType==ConnectivityManager.TYPE_MOBILE){
                    return netState.equals(NetworkInfo.State.CONNECTED);
                }
            }
        }
        return false;
    }

    /**
     * 单例模式获取downlaoder类
     * @param context
     * @return
     */
    public static Downloader getInstance(Context context){
        if(downloader==null){
            if(context!=null){
                downloader=new Downloader(context);
            }
        }
        return downloader;
    }



    /**
     * 给定url地址，存储目录（空为默认目录),监视器接口对象后，触发下载进程。
     * 如果给定的saveFileName所指为所记录的下载条目中的已有内容，则会进入断点
     * 续传的部分，即download会自动根据已下载的进度来决定下载的内容，从而实现
     * 异常中断后仍能在一定下载进度上继续下载。
     *
     * 有关下载线程如何与monitor交互，见{@link AbsDownloadMonitor}
     * @param httpUrl
     * @param saveFileName 确保该目录有效
     * @param monitor
     */
    public void download(String httpUrl, String saveFileName, AbsDownloadMonitor monitor){
        //do a check url work here
        URL httpURL=checkUrl(httpUrl);
        if(httpURL==null){
            monitor.receiveMessage(MessageCode.ERR_URL_FORMAT,"cannot accept that kind of URL");
            monitor.onStop(ProgressMonitor.ProgressState.FAILED);
        }
        //call monitor life cycle function
        DownLoadTask downLoadTask=new DownLoadTask(httpURL,saveFileName,monitor);
        SharedThreadPool.getInstance().executeTask(downLoadTask,SharedThreadPool.PRIORITY.HIGH);

    }

    //simple check url
    private URL checkUrl(String urlspec){
        try {
            URL url=new URL(urlspec);
            if(urlspec.startsWith("http")){
                return url;
            }
            return null;
        } catch (MalformedURLException e) {
            return null;
        }
    }



    private class DownLoadTask implements Runnable{


        /**
         * 概要:
         * 下载任务基本步骤，将传入的元数据与事先记录的元数据进行比对，如果没有发现下载条目的Recorder,
         * 则新建一个Recorder用于存储元数据的信息，
         * 取出元数据到内存中，再分配任务到子线程（实现多路下载), 分配机制则由线程池代为管理,多个线程同时抢占一个容器（借助线程池帮助调度任务）
         * (考虑到worker更多时间是去执行下载任务，而不是抢夺任务。)
         *
         * 如果子任务执行失败,则会通告监视器一个错误码，同时把当前子任务的分块编号加入到失败队列中，以留作未来进一步处理。
         * 文件读写方面： 使用RandomAccessFile,在创建任务时，给它预分配空间，实际写入数据通过文件指针访问对应位置
         */

        private final int CHILD_THREAD_COUNT_LIMIT =3;
        private final int RETRY_HTTP_CONNECTION_LIMIT =3;
        private final long DEFAULT_BLOCK_SIZE=16*1024*1024;
        private final int QUEUE_MAX_LENGTH=5;
        private final int CONNECTION_TIMEOUT_MILLISECOND=FileDownloader.getInstance().CONNECTION_TIMEOUT_MILLISECOND;
        private File downloadDirectory=null;
        private String saveFileName;
        private URL url;
        private AbsDownloadMonitor monitor;
        private boolean cancel=false;
        private List<Integer> incompleteTaskList=new LinkedList<>();

        DownLoadTask(URL url,String saveFileName,AbsDownloadMonitor monitor){
            this.monitor=monitor;
            this.url=url;
            downloadDirectory=new File(FileDownloader.getInstance().defaultDownloadDirectory);
            if(!downloadDirectory.exists()){
                if(!downloadDirectory.mkdirs()){
                    //create directory file
                    monitor.receiveMessage(MessageCode.ERR_CREATE_DIRECTORY,"create directory failed");
                    monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                }
            }
            if(!downloadDirectory.isDirectory()){
                monitor.receiveMessage(MessageCode.ERR_CREATE_DIRECTORY,"cannot find download directory");
                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
            }

            this.saveFileName=getDefaultNameFromUrl(url,saveFileName);
        }


        @Override
        public void run() {

            //任务执行开始，检查存储是否正常,检查该下载条目是否已经被注册,检查网络可用状态
            monitor.onStart();

            //检查网络是否可用
            if(!isNetWorkAvailable()){
                monitor.receiveMessage(MessageCode.ERR_NETWORK_UNAVAILABLE,"Check the network state");
                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                return;
            }

            File saveFile=new File(downloadDirectory,saveFileName);

            DownloadEntryRecorder entryRecorder=app.getRecorderFactory().getDownloadEntryRecorder();

            //查询是否存在该条目, getDownloadRecorder 提供尽力而为的服务,不存在则添加该下载项
            DownloadRecorder downloadRecorder=entryRecorder.getDownloadRecorder(saveFile.getName());
            if(downloadRecorder==null){
                //raise to ui
                monitor.receiveMessage(MessageCode.ERR_GET_DOWNLOAD_METADATA,"cannot get download recorder, internal error");
                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                return;
            }


            //检索条目元信息，如果有信息，则将信息更新到内存中，并配置启动子任务的参数，如果没有，则建立http连接获取基本元信息
            final String urlStr=downloadRecorder.getUrl();
            final String recordPathName=downloadRecorder.getPathName();
            long fileSize=downloadRecorder.getFileSize();
            long blockCount=downloadRecorder.getBlockCount();


            final boolean haveHead=recordPathName!=null&&recordPathName.contentEquals(saveFile.getAbsolutePath()) && (urlStr!=null && !urlStr.isEmpty());
            final boolean haveValidMetaData= (haveHead) && ( fileSize>0 ) && (blockCount>0);

            if(haveValidMetaData){ //断点续传代码
                //这里开始初始化下载参数，块的下载进度，调用#AbsDownloadMonitor.onProgress回馈信息
                try {
                    if (blockCount == 1) {
                        //单线程作业
                        HttpURLConnection http = (HttpURLConnection) url.openConnection();
                        RandomAccessFile randomAccessFile=new RandomAccessFile(saveFile,"rwd");
                        long req_start=downloadRecorder.getBlockInfo(1); //当前已经有效的信息长度,req_start指明偏移量
                        req_start= req_start ==-1? 0 :req_start; //req_start不能为负值
                        final long req_end=fileSize-1; //req_end为最后一个字节的索引值
                        DownloadSubTask singleTask = new DownloadSubTask(http,randomAccessFile,1,req_start,req_end,RETRY_HTTP_CONNECTION_LIMIT,downloadRecorder,monitor);
                        singleTask.run();

                        //检查是否有报告异常
                        if(incompleteTaskList.isEmpty()){
                            downloadRecorder.markFinished(true);
                            downloadRecorder.commit();
                            monitor.onFinished();
                        }
                        else{
                            monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                        }
                    }
                    else {

                        //这里遵循一个原则,最后写入的才是可信的，查找需要重传的块
                        final long tail_length=fileSize%DEFAULT_BLOCK_SIZE;

                        //多线程化, 这里自定义阻塞队列,以及拒绝处理
                        BlockingQueue<Runnable> blockingQueue=prepareQueue(QUEUE_MAX_LENGTH);
                        //如果任务拒绝则在主任务线程中执行，线程没有工作则存活一秒，一般情况下不会遇到
                        ThreadPoolExecutor executor=new ThreadPoolExecutor(CHILD_THREAD_COUNT_LIMIT,Integer.MAX_VALUE,1000, TimeUnit.MILLISECONDS,blockingQueue,new ThreadPoolExecutor.CallerRunsPolicy());

                        //检查文件大小是否预分配了空间
                        RandomAccessFile checkFile=new RandomAccessFile(saveFile,"rwd");
                        if(checkFile.length()<fileSize){
                            checkFile.setLength(fileSize);
                        }
                        checkFile.close();


                        for (int i = 1; i <= blockCount; i++) {
                            long block_valid_length = downloadRecorder.getBlockInfo(i);
                            //查看有效段是否完整，不完整准备重新请求
                            if( block_valid_length<(i < blockCount ? DEFAULT_BLOCK_SIZE : tail_length) ) {
                                block_valid_length=block_valid_length<0 ? 0L :block_valid_length;
                                //检查中断信号与线程池负载情况
                                boolean task_dispatched=false;
                                while (!task_dispatched && !checkInterruptAndAbortSignal()) {
                                    //任务未完成，按需加入线程池
                                    if(executor.getActiveCount()<CHILD_THREAD_COUNT_LIMIT) {
                                        final long req_start = (i - 1) * DEFAULT_BLOCK_SIZE+block_valid_length; //起始偏移量为块偏移加上内碎片偏移
                                        final long req_end = (i < blockCount ? i*DEFAULT_BLOCK_SIZE : fileSize-1) - 1; //尾部偏移为当前块偏移-1或者文件尾部-1
                                        final HttpURLConnection http = (HttpURLConnection) url.openConnection();
                                        RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile, "rwd");

                                        DownloadSubTask subTask = new DownloadSubTask(http, randomAccessFile, i, req_start, req_end, RETRY_HTTP_CONNECTION_LIMIT, downloadRecorder, monitor);
                                        executor.execute(subTask);
                                        task_dispatched = true;
                                    }
                                    else{
                                        try{
                                            Thread.sleep(1000);
                                        }catch (InterruptedException e){
                                            //忽略此异常
                                        }
                                    }
                                }
                                //任务终止
                                if(cancel){
                                    break;
                                }
                            }
                        }

                        if(cancel&&monitor.cancelSignal()){
                            //关闭资源
                            cleanUpTaskRecord(entryRecorder,saveFile.getAbsolutePath());
                            executor.shutdownNow();
                            monitor.onStop(ProgressMonitor.ProgressState.ABORTED);
                        }
                        else {
                            //所有下载任务提交完毕，主任务线程关闭线程池退出
                            executor.shutdown();
                            //等待所有任务执行完毕后，退出
                            executor.awaitTermination(1, TimeUnit.DAYS);// wait forerver

                            if(incompleteTaskList.isEmpty()) {
                                downloadRecorder.markFinished(true);
                                downloadRecorder.commit();
                                monitor.onFinished();
                            }
                            else{
                                //将未成功的块信息写入，也不需要写入，因为如果写入发生重启，app将很可能根据错误的"不完成块信息"进行重传
                                monitor.receiveMessage(MessageCode.NOTICE_ERROR_TASK_COUNT,incompleteTaskList.size());
                                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                            }

                        }

                    }

                }catch (InterruptedException e){
                    monitor.receiveMessage(MessageCode.ERR_INTERRUPT_EXCEPTION,e.getMessage());
                    monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                }
                catch (IOException e) {
                    monitor.receiveMessage(MessageCode.ERR_IO_EXCEPTION,e.getMessage());
                    monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                }
            }
            else{
                //这是新任务
                //建立https连接，只获取头部信息
                try {
                    HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                    //设置请求报文头信息
                    setCommonConnectionHeader(httpURLConnection,"GET",url.getHost()); //referred 设置为了url host地址

                    //尝试建立首次连接
                    int retry=0;
                    while(retry<=RETRY_HTTP_CONNECTION_LIMIT) {
                        try {
                            if(checkInterruptAndAbortSignal()){
                                //有cancel信号,结束任务
                                cleanUpTaskRecord(entryRecorder,saveFile.getAbsolutePath());
                                monitor.onStop(ProgressMonitor.ProgressState.ABORTED);
                                return;
                            }
                            httpURLConnection.connect();
                            break; //连接成功，跳出重连循环。
                        } catch (SocketTimeoutException e) {
                            monitor.receiveMessage(MessageCode.ERR_SOCKET_TIMEOUT,e.getMessage());
                            retry++;
                            //检查重试次数是否超过限制，超过则终止任务
                            if(retry>RETRY_HTTP_CONNECTION_LIMIT){
                                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                                return;
                            }
                            monitor.receiveMessage(MessageCode.NOTICE_HTTP_RECONNECT,retry);
                        }catch (InterruptedException e){
                            //线程睡眠时受到中断信号
                            monitor.receiveMessage(MessageCode.ERR_INTERRUPT_EXCEPTION,e.getMessage());
                            monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                            return;
                        }
                    }

                    //连接成功后，获取头部信息
                    final int httpResponseCode = httpURLConnection.getResponseCode();
                    monitor.receiveMessage(MessageCode.NOTICE_HTTP_RESPONSE_CODE,httpResponseCode);
                    if(httpResponseCode==HttpURLConnection.HTTP_OK){
                        fileSize= httpURLConnection.getContentLengthLong();
                        monitor.onProgress(0,fileSize); //汇报总大小
                        final String contentType=httpURLConnection.getContentType();
                        final String contentEncoding=httpURLConnection.getContentEncoding();


                        //说明数据有效，准备分块信息以及编码流
                        if(fileSize>0){
                            //检查内存空间大小，如果不够大则通知顶层，
                            if(fileSize>emulated_0_root.getUsableSpace()){
                                monitor.receiveMessage(MessageCode.ERR_NO_ENOUGH_SPACE,"no enough space to store file");
                                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                                return;
                            }

                            //计算分块数量
                            blockCount=fileSize/DEFAULT_BLOCK_SIZE +1;

                            //记录分块信息,文件大小信息
                            downloadRecorder.recordBlockCount(blockCount);
                            downloadRecorder.recordFileSize(fileSize);
                            downloadRecorder.markFinished(false);
                            downloadRecorder.recordPath(saveFile.getAbsolutePath());
                            downloadRecorder.recordURL(url.toString());
                            downloadRecorder.recordBlockSize((int) DEFAULT_BLOCK_SIZE);
                            downloadRecorder.commit();

                            //准备分配空间前，再次检查是否有中断信号
                            try{
                                if(checkInterruptAndAbortSignal()){
                                    //有cancel信号,结束任务
                                    cleanUpTaskRecord(entryRecorder,saveFile.getAbsolutePath());
                                    monitor.onStop(ProgressMonitor.ProgressState.ABORTED);
                                    return;
                                }

                            }catch (InterruptedException e){
                                monitor.receiveMessage(MessageCode.ERR_INTERRUPT_EXCEPTION,e.getMessage());
                                monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                                return;
                            }

                            //预分配空间
                            monitor.receiveMessage(MessageCode.NOTICE_RESERVING_SPACE,"reserving space");
                            if(!saveFile.exists()){
                                saveFile.createNewFile();
                            }
                            RandomAccessFile rwFile=new RandomAccessFile(saveFile,"rwd");
                            rwFile.setLength(fileSize); //atomic operation
                            monitor.receiveMessage(MessageCode.NOTICE_RESERVED_SPACE,"reserved space");

                            final boolean gzipEncoding=contentEncoding!=null && contentEncoding.contentEquals("gzip");
                            //根据分块，按需构建子任务.
                            if(blockCount==1){
                                //只有一个块，单线程(此线程)执行此工作
                                DownloadSubTask downloadSubTask=new DownloadSubTask(httpURLConnection,rwFile,1,0,fileSize,3,downloadRecorder,monitor);
                                downloadSubTask.setHeader=false;
                                downloadSubTask.run(); //在子任务中完成进度的记录和汇报
                                if(incompleteTaskList.isEmpty()) {
                                    downloadRecorder.markFinished(true);
                                    downloadRecorder.commit();
                                    monitor.onFinished();
                                }
                                else{
                                    monitor.onStop(ProgressMonitor.ProgressState.FAILED); //标记此任务以失败结束
                                }
                            }
                            else{
                                //重置http连接
                                httpURLConnection.disconnect();
                                httpURLConnection=null;
                                //关闭第一个RandomAccessFile
                                rwFile.close();
                                rwFile=null;

                                //多线程化, 这里自定义阻塞队列,以及拒绝处理
                                BlockingQueue<Runnable> blockingQueue=prepareQueue(QUEUE_MAX_LENGTH);
                                //如果任务拒绝则在主任务线程中执行，线程没有工作则存活一秒，一般情况下不会遇到
                                ThreadPoolExecutor executor=new ThreadPoolExecutor(CHILD_THREAD_COUNT_LIMIT,Integer.MAX_VALUE,1000, TimeUnit.MILLISECONDS,blockingQueue,new ThreadPoolExecutor.CallerRunsPolicy());

                                //符合添加条件，且还有任务则添加任务到线程池中
                                int remainTaskCount= (int) blockCount;
                                long req_start=0L; long req_end=0L;
                                long block_number=1L;

                                while(remainTaskCount>0&&!checkInterruptAndAbortSignal()){
                                    if(executor.getActiveCount()<CHILD_THREAD_COUNT_LIMIT) {
                                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                                        req_start = (blockCount - remainTaskCount) * DEFAULT_BLOCK_SIZE;
                                        if (remainTaskCount == 1) {
                                            //this is a tail block
                                            req_end = req_start + fileSize % DEFAULT_BLOCK_SIZE-1;
                                        } else {
                                            req_end = req_start + DEFAULT_BLOCK_SIZE - 1;
                                        }

                                        //创建随机读写文件
                                        RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile, "rwd");

                                        //创建子任务
                                        DownloadSubTask subTask = new DownloadSubTask(conn, randomAccessFile, block_number,req_start, req_end, RETRY_HTTP_CONNECTION_LIMIT, downloadRecorder, monitor);
                                        block_number++;
                                        executor.execute(subTask);
                                        remainTaskCount--;
                                    }
                                    else{
                                        //队列满，进入睡眠等待
                                        try {
                                            Thread.sleep(1000);
                                        } catch (InterruptedException e) {
                                            //忽略此中断异常
                                        }
                                    }

                                }

                                if(cancel&&monitor.cancelSignal()){
                                    //关闭资源
                                    cleanUpTaskRecord(entryRecorder,saveFile.getAbsolutePath());
                                    executor.shutdownNow();
                                    monitor.onStop(ProgressMonitor.ProgressState.ABORTED);
                                }
                                else {
                                    //所有下载任务提交完毕，主任务线程关闭线程池退出
                                    executor.shutdown();
                                    //等待所有任务执行完毕后，退出
                                    executor.awaitTermination(1, TimeUnit.DAYS);// wait forerver

                                    if(incompleteTaskList.isEmpty()) {
                                        downloadRecorder.markFinished(true);
                                        downloadRecorder.commit();
                                        monitor.onFinished();
                                    }
                                    else{
                                        //将未成功的块信息写入，也不需要写入，因为如果写入发生重启，app将很可能根据错误的"不完成块信息"进行重传
                                        monitor.receiveMessage(MessageCode.NOTICE_ERROR_TASK_COUNT,incompleteTaskList.size());
                                        monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                                    }

                                }

                            }

                        }
                        else{
                            //请求 content长度不大于0
                            monitor.receiveMessage(MessageCode.ERR_NO_VALID_DATA,"http response's content-length is 0");
                            monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                        }
                    }
                    else{
                        monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                    }


                }catch (ProtocolException e){
                    monitor.receiveMessage(MessageCode.ERR_HTTP_PROTOCOL_EXCEPTION,e.getMessage());
                    monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                }
                catch (IOException e) {
                    //I/O error occurs
                    monitor.receiveMessage(MessageCode.ERR_IO_EXCEPTION, e.getMessage());
                    monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                } catch (InterruptedException e) {
                    monitor.receiveMessage(MessageCode.ERR_INTERRUPT_EXCEPTION,e.getMessage());
                    monitor.onStop(ProgressMonitor.ProgressState.ABORTED);
                }
            }

        }


        synchronized private void recordFailTask(int block_id){
            incompleteTaskList.add(block_id);
        }

        private void cleanUpTaskRecord(DownloadEntryRecorder entryRecorder,String pathname_key){
            entryRecorder.deleteRecordEntry(pathname_key);
        }

        /**
         * 设置常用请求头参数
         * @param http
         * @param Method
         * @throws ProtocolException
         */
        private void setCommonConnectionHeader(HttpURLConnection http,final String Method,final String referrer) throws ProtocolException {
            http.setRequestMethod(Method);
            http.setConnectTimeout(CONNECTION_TIMEOUT_MILLISECOND);
            http.setReadTimeout(0);
            //accept all type of data
            http.setRequestProperty("Accept","*/*");
            http.setRequestProperty("Accept-Language","zh-CH,zh;q=0.9,en-US;q=0.8,en;q=0.7");
            http.setRequestProperty("Accept-Encoding","gzip,deflate");
            http.setRequestProperty("Referrer",referrer);
            http.setRequestProperty("Connection","Keep-Alive");
            http.setRequestProperty("Charset","UTF-8");
        }


        private void setHttpAcceptRange(HttpURLConnection http,long start,long end) {
            http.setRequestProperty("Range","bytes="+start+"-"+end);
        }

        private BlockingQueue<Runnable> prepareQueue(final int queueMaxLength){
            /**
             * 排队有三种一般策略：
             * 1. 直接切换 一个工作队列的一个很好的默认选择是一个SynchronousQueue ，将任务交给线程，无需另外控制。 在这里，如果没有线程可以立即运行，
             * 那么尝试排队任务会失败，因此将构建一个新的线程。 处理可能具有内部依赖关系的请求集时，此策略可避免锁定。
             * 直接切换通常需要无限制的maximumPoolSizes，以避免拒绝新提交的任务。 这反过来允许无限线程增长的可能性，当命令继续以平均速度比他们可以处理的速度更快地到达时。
             * 2. 无界队列 使用无界队列（例如LinkedBlockingQueue没有预定容量）会导致新的任务，在队列中等待，当所有corePoolSize线程都很忙。 因此，
             * 不会再创建corePoolSize线程。 （因此，最大值大小的值没有任何影响。）每个任务完全独立于其他任务时，
             * 这可能是适当的，因此任务不会影响其他执行; 例如，在网页服务器中。 虽然这种排队风格可以有助于平滑瞬态突发的请求，但是当命令继续达到的平均
             * 速度比可以处理的速度更快时，它承认无界工作队列增长的可能性。
             * 3. 有边界的队列。 有限队列（例如， ArrayBlockingQueue ）有助于在使用有限maxPoolSizes时防止资源耗尽，但可能更难调整和控制。 队列大小
             * 和最大池大小可能彼此交易：使用大队列和小型池可以最大限度地减少CPU使用率，OS资源和上下文切换开销，
             * 但可能导致人为的低吞吐量。 如果任务频繁阻塞（例如，如果它们是I / O绑定），则系统可能能够安排比您允许的更多线程的时间。 使用小型队列通常
             * 需要较大的池大小，这样可以使CPU繁忙，但可能会遇到不可接受的调度开销，这也降低了吞吐量。
             *
             *
             * 这里采用第二种，无界队列，因为无法知道子线程是什么时候执行完任务的
             *
             */
            BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>(queueMaxLength);
            return queue;
        }


        /**
         *
         * @return true if receive cancel signal else continue
         * @throws InterruptedException
         */
        private boolean checkInterruptAndAbortSignal() throws InterruptedException {
            boolean onPausedCalled=false;
            while(monitor.interruptSignal()){
                if(!onPausedCalled){
                    monitor.onPause();
                    onPausedCalled=true;
                }
                if(monitor.cancelSignal()){ //暂停阶段可能会取消任务，
                    cancel=true;
                    return true;
                }
                Thread.sleep(300);
            }
            if(onPausedCalled) {
                monitor.onResume();
            }
            return false;
        }


        private String getDefaultNameFromUrl(URL url,String saveFileName){
            String defaultFileName=saveFileName;
            if(defaultFileName==null||defaultFileName.isEmpty()) {
                defaultFileName=url.getFile();
//                if (defaultFileName!=null&&defaultFileName.isEmpty()) {
//                    Random random = new Random(SystemClock.currentThreadTimeMillis());
//                    defaultFileName = String.valueOf(random.nextInt());
//                }
            }
            return FDFormatter.replaceIllegalChar(defaultFileName);
        }



        /**
         * 子任务定义要下载的块，偏移量，http请求头信息, 随机访问文件的实例
         */
        private class DownloadSubTask implements Runnable{

            HttpURLConnection httpURLConnectionInstance=null;
            RandomAccessFile rwFile=null;
            long req_start=0L;
            long req_end=0L;
            int retryTime;
            DownloadRecorder recorder=null;
            AbsDownloadMonitor monitor=null;
            long block_number;
            boolean setHeader=true;


            DownloadSubTask(HttpURLConnection httpURLConnection, RandomAccessFile rwFile,long block_number,long req_start,long req_end,int retryTime,DownloadRecorder downloadRecorder,AbsDownloadMonitor monitor){
                this.httpURLConnectionInstance=httpURLConnection;
                this.rwFile=rwFile;
                this.req_end=req_end;
                this.req_start=req_start;
                this.retryTime=retryTime;
                this.recorder=downloadRecorder;
                this.monitor=monitor;
                this.block_number=block_number;
            }


            @Override
            public void run() {
                try {
                    //设置头部请求信息
                    if(setHeader) {
                        setCommonConnectionHeader(httpURLConnectionInstance, "GET", url.getHost());
                        setHttpAcceptRange(httpURLConnectionInstance, req_start, req_end);
                    }
                    int retry=0;
                    while(true) {
                        //建立连接，并选择对应的编码流
                        httpURLConnectionInstance.connect();
                        final int responseCode= httpURLConnectionInstance.getResponseCode();
                        //Map<String, List<String>> map=httpURLConnectionInstance.getHeaderFields();
                        if (responseCode == HttpURLConnection.HTTP_PARTIAL||responseCode==HttpURLConnection.HTTP_OK) {
                            break;
                        } else if(retry<retryTime){
                            retry++;
                            monitor.receiveMessage(MessageCode.NOTICE_HTTP_RESPONSE_CODE, responseCode);
                            monitor.receiveMessage(MessageCode.NOTICE_HTTP_RECONNECT,retry);
                        }
                        else{
                            monitor.receiveMessage(MessageCode.ERR_OPEN_HTTP_CONNECTION,responseCode);
                            recordFailTask((int) block_number);
                            return;
                        }
                    }
                    
                    //连接建立成功，检查编码情况
                    final String CONTENT_ENCODING=httpURLConnectionInstance.getContentEncoding();
                    final boolean gzip_enabled=(CONTENT_ENCODING!=null )&& (CONTENT_ENCODING.contentEquals("gzip"));

                    //执行传输过程
                    process_transmission(httpURLConnectionInstance.getInputStream(),gzip_enabled);
                    //传输完毕后，关闭读写文件
                    rwFile.close();
                    
                }catch (ProtocolException e){
                    //处理协议头异常
                    recordFailTask((int) block_number);
                    monitor.receiveMessage(MessageCode.ERR_HTTP_PROTOCOL_EXCEPTION,e.getMessage());
                }catch (InterruptedException e){
                    //中断异常
                    recordFailTask((int) block_number);
                    monitor.receiveMessage(MessageCode.ERR_INTERRUPT_EXCEPTION,e.getMessage());
                }
                catch (IOException e) {
                    //IO异常，这个异常需要重视，因为它意味者传输流提前被中断了
                    recordFailTask((int) block_number);
                    monitor.receiveMessage(MessageCode.ERR_IO_EXCEPTION,e.getMessage());
                    //monitor.onStop(ProgressMonitor.ProgressState.FAILED);
                }finally {
                    recorder=null;
                    httpURLConnectionInstance.disconnect();
                }

            }

            private void process_transmission(InputStream inputStream, final boolean gzip) throws IOException, InterruptedException {
                if(!cancel&&!monitor.cancelSignal()) {
                    InputStream input = inputStream;
                    if (gzip) {
                        input = new GZIPInputStream(input);
                    }

                    //read write buffer
                    final int BUFFER_SIZE = 1024 * 512; //16KB
                    final long content_length = req_end - req_start + 1;
                    final long block_count = content_length / BUFFER_SIZE; //不包括尾部
                    final int tail_length = (int) (content_length % BUFFER_SIZE);

                    int block_transferred = 0;
                    long bytes_transferred = 0L;

                    rwFile.seek(req_start);

                    Log.d("FilePtr", "process_transmission: "+block_number+": "+rwFile.getFilePointer());

                    byte[] buffer=new byte[BUFFER_SIZE];
                    int round=0;
                    final long block_length_finished=req_start-(block_number-1)*DEFAULT_BLOCK_SIZE;
                    long block_progress=block_length_finished+block_length_finished; //块整体进度
                    //这里循环写入数据
                    while (block_transferred < block_count) {

                        if (checkInterruptAndAbortSignal()) {
                            return; //退出子线程
                        }
                        int byte_in_buffer_transferred=0;


                        //read函数特性
                        while(byte_in_buffer_transferred<BUFFER_SIZE){
                            int read_result=input.read(buffer,byte_in_buffer_transferred,BUFFER_SIZE-byte_in_buffer_transferred);
                            if(read_result==-1){
                                if(byte_in_buffer_transferred>=BUFFER_SIZE-1) {
                                    break;
                                }
                                else{
                                    //因为其它异常，导致流提前终止
                                    //保存信息后，终止流程,下次传输为断点启动
                                    recordFailTask((int) block_number);
                                    rwFile.close();
                                    httpURLConnectionInstance.disconnect();
                                    buffer=null;
                                    return;
                                }
                            }
                            byte_in_buffer_transferred+=read_result;
                        }

                        rwFile.write(buffer,0,BUFFER_SIZE);
                        bytes_transferred += BUFFER_SIZE;
                        if (checkInterruptAndAbortSignal()) {
                            return; //退出子线程,不需要再写，因为任务被取消
                        }

                        //bug: 进度应该根据当前块更新
                        block_progress=block_length_finished+bytes_transferred;
                        monitor.onProgress((int) block_number,block_progress);

                        if(round>3) {
                            round=0;
                            recorder.recordBlockInfo(block_progress, block_number);
                            recorder.commit();
                        }
                        round++;
                        block_transferred++;

                    }

                    recorder.recordBlockInfo(block_progress, block_number);
                    recorder.commit();
                    if (tail_length > 0) {
                        if(checkInterruptAndAbortSignal()){
                            return;
                        }
                        int byte_in_tail_transferred=0;
                        while(byte_in_tail_transferred<tail_length){
                            int read_result=input.read(buffer,byte_in_tail_transferred,tail_length-byte_in_tail_transferred);
                            //需要考虑，如果流被异常关闭了，这个值应该是也会是-1，对于应该要提交的写操作需要注意。
                            if(read_result==-1){
                                break;
                            }
                            byte_in_tail_transferred+=read_result;
                        }
                        rwFile.write(buffer,0,tail_length);
                        bytes_transferred+=tail_length;
                        block_progress=block_length_finished+bytes_transferred;
                        monitor.onProgress((int) block_number,block_progress);
                        recorder.recordBlockInfo(bytes_transferred,block_number);
                        recorder.commit();
                    }
                    buffer=null;
                }
                else{
                    cancel=true;
                }
            }

        }
    }


    /**
     * 定义通知UI显示，以及一些常用状态的消息码
     */
    public enum MessageCode{
        /**
         * 不接受此url请求
         */
        ERR_URL_FORMAT,
        /**
         * 创建目录失败
         */
        ERR_CREATE_DIRECTORY,
        /**
         * 没有剩余空间
         */
        ERR_NO_ENOUGH_SPACE,
        /**
         * 无法配置下载元信息
         */
        ERR_GET_DOWNLOAD_METADATA,
        /**
         * 启动HTTP 连接失败
         */
        ERR_OPEN_HTTP_CONNECTION,
        /**
         * 连接重试通知
         */
        NOTICE_HTTP_RECONNECT,
        /**
         * 通知I/O错误
         */
        ERR_IO_EXCEPTION,
        /**
         * 设置协议头错误
         */
        ERR_HTTP_PROTOCOL_EXCEPTION,
        /**
         * 网络不通
         */
        ERR_NETWORK_UNAVAILABLE,
        /**
         * 中断错误
         */
        ERR_INTERRUPT_EXCEPTION,
        /**
         * socket建立连接超时
         */
        ERR_SOCKET_TIMEOUT,
        /**
         * Http 响应码
         */
        NOTICE_HTTP_RESPONSE_CODE,
        /**
         * 表示请求的源无法给出有效数据
         */
        ERR_NO_VALID_DATA,
        /**
         * 预分配空间进行提示
         */
        NOTICE_RESERVING_SPACE,
        NOTICE_RESERVED_SPACE,
        /**
         * 通知错误子任务的数量
         */
        NOTICE_ERROR_TASK_COUNT,
    }
}
